use std::collections::VecDeque;

use wither::{
    bson::{doc, oid::ObjectId, Document},
    mongodb::options::{FindOneAndUpdateOptions, ReturnDocument},
    Model,
};

use crate::{
    errors::cons_error,
    mdb::{ModelEx, MDB},
    models::{
        profile::{ProfileModel, ProfileModelDataHistory},
        user::SimpleUserModel,
    },
    utils::traits::UpdateModelData,
    AppError, AppResult, Pagination, PaginationQuery,
};

pub mod service_struct {
    use crate::{
        models::profile::ProfileModel,
        utils::{db_utils::UpdateField, traits::UpdateModelData},
        AppError,
    };
    use serde::{Deserialize, Serialize};
    use wither::bson::{doc, oid::ObjectId, Document};

    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct UpdateProfileSvrReq {
        pub profile_id: ObjectId,
        pub namespace: Option<String>,
        pub data: UpdateField<Document>,
        pub max_history: Option<u8>,
    }

    impl UpdateModelData for UpdateProfileSvrReq {
        type Error = AppError;
        fn get_update_set_doc(&self) -> Result<Document, Self::Error> {
            Ok(option_doc!(
                "namespace":self.namespace,
                "data":self.data.to_bson()?,
                "max_history":self.max_history.map(|v| v as u32)
            ))
        }
        fn get_update_filter_doc(&self) -> Result<Document, Self::Error> {
            Ok(doc! {
                "_id":self.profile_id
            })
        }
    }
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct CreateProfileSvrReq {
        pub namespace: String,
        pub data: Option<Document>,
        pub max_history: u8,
    }
    impl From<CreateProfileSvrReq> for ProfileModel {
        fn from(r: CreateProfileSvrReq) -> Self {
            Self {
                namespace: r.namespace,
                data: r.data,
                max_history: r.max_history,
                ..Default::default()
            }
        }
    }
}
pub use service_struct::*;

#[derive(Debug, Clone)]
pub struct ProfileService {
    mdb: MDB,
}

impl ProfileService {
    pub fn new(mdb: MDB) -> Self {
        Self { mdb }
    }

    pub async fn detail<I: Into<ObjectId>>(&self, id: I) -> AppResult<ProfileModel> {
        let id: ObjectId = id.into();
        ProfileModel::find_by_id(&*self.mdb, id, None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))
    }

    pub async fn search<F: Into<Option<Document>>>(
        &self,
        filter: F,
        query: &PaginationQuery,
    ) -> AppResult<Pagination<ProfileModel>> {
        let filter: Option<Document> = filter.into();
        ProfileModel::find_and_pagination(self.mdb.clone(), filter, query).await
    }

    pub async fn update<T: Into<service_struct::UpdateProfileSvrReq>, O: Into<SimpleUserModel>>(
        &self,
        operator: O,
        req: T,
    ) -> AppResult<ProfileModel> {
        let req: service_struct::UpdateProfileSvrReq = req.into();
        let filter = req.get_update_filter_doc()?;
        let mut set = req.get_update_set_doc()?;
        let old_model = ProfileModel::find_one(&*self.mdb, filter.clone(), None)
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        if !req.data.is_unchanged() {
            let mut history = old_model.history.into_iter().collect::<VecDeque<_>>();
            let max_history = old_model.max_history;
            if history.len() >= max_history as usize {
                history.pop_front();
            }
            history.push_back(ProfileModelDataHistory::new(operator, old_model.data));
            set.insert("history", history.into_iter().collect::<Vec<_>>());
        }
        if !set.is_empty() {
            set.insert("updated_at", wither::bson::DateTime::now());
        }
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        ProfileModel::find_one_and_update(
            &*self.mdb,
            filter,
            doc! {
                "$set":set
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))
    }
    pub async fn create<T: Into<CreateProfileSvrReq>>(&self, req: T) -> AppResult<ProfileModel> {
        let req: CreateProfileSvrReq = req.into();
        let mut model = ProfileModel::from(req);
        model.save(&*self.mdb, None).await?;
        Ok(model)
    }
}
